import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.sql.Time;
import java.util.Calendar;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.Map.Entry;
import javax.crypto.SecretKey;

/*
 * This is the CentralServer -- Karthik
 */

public class CentralServer extends UnicastRemoteObject implements CentralServerInterface
{
	private static final String FileName = "config.ini";
	static String directoryName = "file_dir";
	int dSync;
	private static File dir;
	private static File[] fileList;
	private static HashMap<String, Integer> fileListLocks;// filename and the read(no of readers) or write lock
	private static HashMap<String, Vector<ClientObj>> fileListVectors;// filename and waiting clients
	static HashMap<Long, Boolean> triggeredMap;
	static long currentThreadId = 0;
	private static HashMap<String,CurrentServerLoad> fileLocks; // Name of file and list of Servers
	private static HashMap<String,Vector<FileServer>> fileServer;// Name of file and where the chunks are stored
	private static HashMap<String,ServerLoadInfo> dServerLoadInfo; // servername and the server load info
	private static HashMap<String,Long> dListOfServers;	
	private static HashMap<String, SecretKey> dFileKeys;
	
	
	public CentralServer() throws IOException
	{
		//CreateDirectory();
		fileListLocks = new HashMap<String,Integer>();
		fileListVectors = new HashMap<String,Vector<ClientObj>>();
		triggeredMap = new HashMap<Long, Boolean>();
		fileLocks = new HashMap<String,CurrentServerLoad>();
		fileServer = new HashMap<String,Vector<FileServer>>();
		dServerLoadInfo = new HashMap<String,ServerLoadInfo>();
		//dListOfServers = new Vector<String>();
		dListOfServers = new HashMap<String, Long>();
		dFileKeys = new HashMap<String, SecretKey>();
		
		//mReadConfigFile();
		
	}
	public void mUpdateTimeStamp(String xHostName, String xPort) throws RemoteException
	{
		if(dListOfServers.containsKey(xHostName + " " + xPort))
		{
			dListOfServers.put(xHostName+" "+xPort, Calendar.getInstance().getTimeInMillis());
		}
		else
		{
			System.out.println("ERROR: Updating timestamp of Unknown host");
		}
	}
	private void mReadConfigFile() throws IOException 
	{
		/*
		FileInputStream fis = new FileInputStream(FileName);
		BufferedReader xbr = new BufferedReader(new InputStreamReader(fis));
		String xhostName;
		while((xhostName = mReadNextLine(xbr)) != null)
		{	
			String serverPort = mReadNextLine(xbr);
			//dListOfServers.add(xhostName + " " + serverPort);
			ServerLoadInfo xload = new ServerLoadInfo();// initialize the load on each server
			dServerLoadInfo.put(xhostName + " " + serverPort, xload);
		}
		*/
		
	}
	
	private static String mReadNextLine(BufferedReader pbr) throws IOException 
	{

		StringTokenizer xTok;
		String xStr = pbr.readLine();
		if(xStr == null)
			return null;
		else
		{
			xTok = new StringTokenizer(xStr);
			xTok.nextToken();
			return xTok.nextToken();
		}

	}

	private boolean mWaitForLock(long pId) throws InterruptedException 
    {
            
            while(true)
            {
                    synchronized (triggeredMap) 
                    {
                    		System.out.println("Waiting for lock " + pId);
                            triggeredMap.wait();
                            if(mIsTriggered(pId))
                            {
                                    triggeredMap.remove(pId);
                                    break;
                            }
                    }
            }
            
            return true;
    }

	
	public boolean mIsLocked(String pFileName, boolean isReader, int xPriority)throws RemoteException, InterruptedException 
	{
		//mPrintFunction();

		int xlock;
		
		System.out.println("fileName and Lock " + fileListLocks);
	    
		/*
		 * Incoming process is Reader
		 */
		
		long xId = currentThreadId++; // This is a substitute for the thread id
		
		ClientObj pClientObj = mCreateClientObj(xId,xPriority,isReader);
		
		if(pClientObj.isReader)
		{
	    	//	System.out.println("Inside READER " + pFileName);
			
			if(fileListLocks.containsKey(pFileName))
			{
				xlock = (int) fileListLocks.get(pFileName);

				// Case 1 - Read Lock on the file
				
				if(xlock != 0)
				{
					//Check if higher priority writer is waiting
					if(mCheckWriterPriority(pFileName, pClientObj.priority))
					{
						// Put this process in the vector
						mPutIntoVector(pFileName,pClientObj);
						//return false;
						return mWaitForLock(xId);
					}
					else
					{
						// No higher priority writer is waiting so give access to read
						xlock = fileListLocks.get(pFileName);
						++xlock;
						fileListLocks.put(pFileName, xlock);
						return true;
					}

				}
				// Case 2 - Write lock on the file 
				else if(xlock == 0)
				{
					// Put this process in the vector
					mPutIntoVector(pFileName,pClientObj);
					//return false;
					return mWaitForLock(xId);
				}
			}
			// Case 3 - No lock give access, add the lock to the fileListLocks hashmap
			else
			{
				
				fileListLocks.put(pFileName, 1);
				return true;
			}

		}
		/*
		 * Incoming process is Writer
		 */
		else
		{
			// Case 1 & 2 - if there is a lock on the file then put this process in vector
			if(fileListLocks.containsKey(pFileName))
			{
				// Put this process in the vector
				mPutIntoVector(pFileName, pClientObj);
				//return false;
				System.out.println("Waiting");
				return mWaitForLock(xId);
			}
			else
			{
				fileListLocks.put(pFileName, 0);
				return true;
			}
		}
		
		return false;
	}
	public void mRegister(String xHostName, String xPort)
	{
		System.out.println("Inside Register");
		
		if(xHostName!= null && xPort !=null)
		if(!dListOfServers.containsKey(xHostName + " " + xPort))
		{
			System.out.println("Inside Register " +  xHostName + " " + xPort);
			//dListOfServers.add(xHostName + " " + xPort);
			dListOfServers.put(xHostName + " " + xPort,Calendar.getInstance().getTimeInMillis());
			ServerLoadInfo xload = new ServerLoadInfo();// initialize the load on each server
			dServerLoadInfo.put(xHostName + " " + xPort, xload);
		}
	}
    public String mGetServers(String pFileName, long pFileSize,int xPriority,boolean xIsReader) throws RemoteException 
	{
    	String xReturn = "";
        String xlength = "";

        System.out.println("mGetServers    " + fileServer);
        System.out.println("mFilelength " +  pFileSize);
       
        
        mCheckAndRemoveServers();

        if (xIsReader)
        {
                xReturn = mGetLocation(pFileName);

        }
        else
        {
        		//Assuming here that we create  Only one Copy of the File
                xReturn = ""+ dListOfServers.size()/2;
                int xLength = (int)(pFileSize/(1024*dListOfServers.size()/2));


                Iterator<String> lListServers = dListOfServers.keySet().iterator();
                int i=0;
                while(lListServers.hasNext())
                {
                        String xhostName = lListServers.next();
                        String xhostNameBuf = lListServers.next();
                        if(i == dListOfServers.size()/2 -1)
                        {
                                int lLeftFile = (int)pFileSize - xLength*1024*(dListOfServers.size()/2 -1);
                                xLength = lLeftFile/1024;
                                int lRem = lLeftFile%1024;
                                if(lRem >0)
                                        xLength++;
                        }
                        System.out.println(" REEEE" +xLength);
                        xReturn = xReturn + ":" +xhostName + " " + Math.abs(xLength);
                        xReturn = xReturn + ":" +xhostNameBuf + " " + Math.abs(xLength);
                        i+=2;
                        //ServerLoadInfo xload = dServerLoadInfo.get(xhostName);// Update the processinginfo for each hostname
                        //xload.processingInfo.put(pFileName, Integer.parseInt(xlength));
                        //dServerLoadInfo.put(xhostName, xload);
                }
        }


		mUpdateFileLocks(xReturn,pFileName);
    	
		System.out.println("The returning string is " + xReturn);
		return xReturn;
	}


    
	private void mCheckAndRemoveServers() 
	{
		 Iterator<String> lListServers = dListOfServers.keySet().iterator();
		 Vector<String> xToRemove = new Vector<String>();
         long xCurrentTime = Calendar.getInstance().getTimeInMillis();
         
         while(lListServers.hasNext())
         {
        	 String xhostName = lListServers.next();
        	 long xTimeStamp = dListOfServers.get(xhostName);
		 System.out.println(xhostName+ " " +xTimeStamp + " " + xCurrentTime);		
		 System.out.println(xTimeStamp-xCurrentTime);
        	 if((xCurrentTime - xTimeStamp) > 10000)
		 {
        		 xToRemove.add(xhostName);
			System.out.println(xhostName);
		}
         }
		for(int i =0;i<xToRemove.size();++i)
		{
			dListOfServers.remove(xToRemove.elementAt(i));
		}
         
	}
	private String mGetLocation(String pFileName) 
	{
		Vector<FileServer> xlistOfServers = fileServer.get(pFileName);
		String xReturn = "" + xlistOfServers.size();
		boolean lServerFound = false;
		for(int i =0; i < xlistOfServers.size();++i)
		{
			FileServer xServer = xlistOfServers.elementAt(i);
			Vector<FileListServer> lFileList = xServer.dFileList;
			for(int j =0 ; j< lFileList.size(); ++j)
			{
				String lHostName = lFileList.elementAt(j).mGetHostName();
				if(dListOfServers.containsKey(lHostName))
				{
					xReturn = xReturn + ":" + lHostName+ " " + xServer.numChunks;			
					lServerFound = true;
					break;	
				}	
			}		
			
		}
		if(!lServerFound)
			System.out.println("Server  for this file not Found");
		return xReturn;
	}

	private void mUpdateFileLocks(String pString, String pFileName) 
	{
		CurrentServerLoad xload   = new CurrentServerLoad(pString);
		System.out.println("fileName " + pFileName + "hostNames " + xload.hostNames);
		fileLocks.put(pFileName, xload);
			
	}
	private void mUpdateFileServers(String pString,String pFileName)
	{
		String [] lServers = pString.split(":");
		System.out.println("++++++++++++++++" + pString);
		int xlength = Integer.parseInt(lServers[0]);
		Vector<FileServer> lVector  = new Vector<FileServer>();
		for(int i =1 ; i <= xlength ;++i)
		{
			String[] lHostAndLoad =  lServers[2 * i -1].split(" ");
			String[] lHostAndLoadBak  =  lServers[2 * i ].split(" ");
			String lHostName =  lHostAndLoad[0] + " " + lHostAndLoad[1];
			String lHostNameBak =  lHostAndLoadBak[0] + " " + lHostAndLoadBak[1];
			int lChunkSize  = Integer.parseInt(lHostAndLoad[2]);
			FileServer lNewObj =  new FileServer(lChunkSize);
			FileListServer lFileList = new FileListServer(lHostName);
			FileListServer lFileListBak = new FileListServer(lHostNameBak);
			
			lNewObj.dFileList.add(lFileList);
			lNewObj.dFileList.add(lFileListBak);
			lVector.add(lNewObj);
		}
		fileServer.put(pFileName, lVector);
		
	}
	
	synchronized
	public void mUpdateLock(String pFileName,String pHostName,boolean xIsPut) throws RemoteException 
	{
		//mUpdateLoadInfo(pFileName, pHostName);
		//System.out.println("" + pFileName + " " + pHostName);
		CurrentServerLoad xload = fileLocks.get(pFileName);
		xload.hostNames.remove(pHostName);
		fileLocks.put(pFileName,xload);
		System.out.println(pHostName + " In UpdateLocks hostnames ... " + xload.hostNames);
		if (xload.hostNames.isEmpty())
		{
			System.out.println("Now unlocking the file");
			if(xIsPut)
				mUpdateFileServers(xload.serverLoad,pFileName);
			mTriggerThreads(pFileName);
		}
		else
		{
			fileLocks.put(pFileName, xload);
		}
		
	}
	

	private void mUpdateLoadInfo(String pFileName, String pHostName) 
	{
		
		ServerLoadInfo xServer = dServerLoadInfo.get(pHostName);
		xServer.currentLoad += xServer.processingInfo.get(pFileName);
		dServerLoadInfo.put(pHostName, xServer);
		
	}
	private static void CreateDirectory() 
	{
		
		File xFile = new File(directoryName);
		if(!xFile.exists())
		{
			xFile.mkdir();
			
		}
				
	}
	private ClientObj mCreateClientObj(long id, int xPriority, boolean pisReader) 
	{
		
		ClientObj xClientObj = new ClientObj(xPriority, id, Calendar.getInstance().getTimeInMillis(), pisReader);
			
		return xClientObj;

		
	}
	private static void mPutIntoVector(String pFileName,ClientObj pClientObj) 
	{
	
		Vector<ClientObj> xVector = new Vector<ClientObj>();
		System.out.println("Added client to vector");
		if(fileListVectors.containsKey(pFileName))
		{
			xVector = fileListVectors.get(pFileName);
			xVector.add(pClientObj);
			fileListVectors.put(pFileName,xVector);
			
		}
		else
		{
			
			xVector.add(pClientObj);
			fileListVectors.put(pFileName,xVector);
		}
		triggeredMap.put(pClientObj.threadId, false);
		
	}
	
	/*
	 *  This function returns a true if there is a writer waiting with higher priority
	 *  else false 
	 */
	private static boolean mCheckWriterPriority(String pFileName, int priority) 
	{
		if(fileListVectors.containsKey(pFileName))
		{
			Vector<ClientObj> xVector = fileListVectors.get(pFileName);
			Iterator<ClientObj> xItr = xVector.iterator();
			while(xItr.hasNext())
			{
				ClientObj xClientObj = xItr.next();
				if(!xClientObj.isReader)
				{
					if(xClientObj.priority > priority)
					{
						return true;
					}
				}
			}
		}
		
		return false;
	}

	synchronized
	public static void mTriggerThreads(String pFileName)
	{
		int xlock = fileListLocks.get(pFileName);
		//System.out.println(xlock + "LOCKKK");
		if(xlock != 0)
		{
			// This means its a Reader
			--xlock;
			fileListLocks.put(pFileName,xlock);
			if(xlock != 0)
				return;
		}
		
		// This means no processes are waiting for this file
		
		if(!fileListVectors.containsKey(pFileName))
		{
			fileListLocks.remove(pFileName);
			System.out.println("In Remove  " + fileListLocks);
			return;
		}
		
		Vector<ClientObj> xVector = fileListVectors.get(pFileName);
		
		Vector<ClientObj> xList = new Vector<ClientObj>();
		ClientObj xHighest = mFindHighestPriority(xVector, xList);
		
		if(!xHighest.isReader)
		{
			xList.clear();
			xList.add(xHighest);
		}
		

		synchronized (triggeredMap) 
		{
			mSchedule(pFileName,xList);
			triggeredMap.notifyAll();
		}
		

        		
	}

	private static ClientObj mFindHighestPriority(Vector<ClientObj> pWaitingList, Vector<ClientObj> pScheduleList) {
		
		Iterator<ClientObj> xItr = pWaitingList.iterator();
		Comparator<ClientObj> xComparator = new ClientObjComparator();
		PriorityQueue<ClientObj> xPQ = new PriorityQueue<ClientObj>(10,xComparator);
		
		while(xItr.hasNext())
		{
			ClientObj xClientObj = xItr.next();
			
			if(xClientObj.isReader)
				pScheduleList.add(xClientObj);
			
			xPQ.add(xClientObj);
			
		}
		ClientObj xTop = xPQ.peek();
		
		return xTop;

	}

	private static void mSchedule(String pFileName, Vector<ClientObj> pScheduleList) 
	{
		System.out.println("Came into Scheduler " + pScheduleList.size());
		
		Vector<ClientObj> xVector = fileListVectors.get(pFileName);
		Iterator<ClientObj> xItr = pScheduleList.iterator();

		while(xItr.hasNext())
		{
			ClientObj xClientObj = xItr.next();
			xVector.remove(xClientObj);
			triggeredMap.put(xClientObj.threadId, true);
		}
		if(xVector.isEmpty())
		{
			fileListVectors.remove(pFileName);
			
		}
		else
			fileListVectors.put(pFileName, xVector);
		
		
	}

	public static boolean mIsTriggered(long id) 
	{
		if(triggeredMap.get(id) == true )
			return  true;
		return false;

	}
	public String mList()throws RemoteException
	{
		Iterator iterator = fileServer.keySet().iterator();
		String xList = null;
		while (iterator.hasNext())
		{
			int i=0;
			String host_chunks=null;
			String key = iterator.next().toString();
			Vector<FileServer> value=fileServer.get(key);
			Iterator vectorIter=value.iterator();
			while(vectorIter.hasNext())
			{
				//TODO 
				// Has to parse through all the Members in the List
				/*
				FileServer obj=value.get(i);
				if(host_chunks==null)
					host_chunks=obj.hostname+ " " +obj.numChunks+" ";
				else
					host_chunks=host_chunks+ obj.hostname+ " " +obj.numChunks+" ";
				i++;
				if(i==value.size())
					break;
				*/
			}
			if(xList==null)
				xList=key + " " + host_chunks+":";
			else
				xList=xList+key + " " + host_chunks+":";
		}
		return xList;
	}


	private static void mPrintFunction() 
	{
		
		
		Set<Entry<String, Integer>> xLockSet = fileListLocks.entrySet();
	
		Iterator<Entry<String, Integer>> xItr1 = xLockSet.iterator();

		System.out.println("fileLocks ------ ");
		System.out.println("FileName : Lock");
		
		while(xItr1.hasNext())
		{
			Map.Entry<String, Integer> me = (Map.Entry<String, Integer>)xItr1.next();
			System.out.println(me.getKey() + " : " + me.getValue() );
			
		}
		
		System.out.println("fileListVectors ------");
		Set<Entry<String, Vector<ClientObj>>> set = fileListVectors.entrySet();
		Iterator<Entry<String, Vector<ClientObj>>> i = set.iterator();

		while(i.hasNext())
		{
			Map.Entry<String, Vector<ClientObj>> me = (Map.Entry<String, Vector<ClientObj>>)(i.next());
			Vector<ClientObj> xVector = (Vector<ClientObj>)( me.getValue());
			System.out.println("FileName : " + me.getKey() + " : ");
			Iterator<ClientObj> xItr = xVector.iterator();
			
			while(xItr.hasNext())
			{
				ClientObj xClientObj = xItr.next();
				System.out.println("Client " + xClientObj.threadId + " " + 
						xClientObj.isReader + " " + xClientObj.priority + " " + xClientObj.timestamp);
			}
		}
		
		System.out.println("Triggered Map ------");
		System.out.println("ThreadId : isTriggered");
		Set<Entry<Long, Boolean>> xTset = triggeredMap.entrySet();
		Iterator<Entry<Long,Boolean>> xItr2= xTset.iterator();
		while(xItr2.hasNext())
		{
			Map.Entry<Long, Boolean> me = (Map.Entry<Long, Boolean>)xItr2.next();
			System.out.println( me.getKey() + " : " + me.getValue());
		}
	}
	
	public SecretKey mGetKey(String pFileName) throws RemoteException 
	{
		if(dFileKeys.containsKey(pFileName))
		{
			return dFileKeys.get(pFileName);
		}
		else
		{
			System.out.println("ERROR: NO KEY ON FILE");
			return null;
		}
		
	}
	
	public void mPutKey(String pFileName, SecretKey pKey) throws RemoteException 
	{
		dFileKeys.put(pFileName, pKey);
		
	}
	
		
}
